package dirsync

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

// 	"github.com/cheggaaa/pb/v3"

// DirProgressCallback 定义进度回调函数类型
//
// 参数:
//   - currentFile: 当前正在处理的文件相对路径
//   - currentFileProgressPercent: 当前文件的进度百分比(0-100)
//   - copiedFiles: 已复制的文件数量
//   - totalFiles: 总文件数量
//   - progressPercent: 当前进度百分比(0-100)
type DirProgressCallback func(DirProgress)

// type DirProgressCallback func(currentFile string, currentFileProgressCallback FileProgressCallback, copiedFiles, totalFiles int64, totalProgressPercent float64)

// SyncDirectories 同步源目录到目标目录，支持文件复制、覆盖和删除多余文件
//
// 功能:
//   - 将源目录中的所有文件和文件夹复制到目标目录
//   - 可选择是否覆盖目标目录中已存在的文件
//   - 可选择是否删除目标目录中存在但源目录不存在的文件和文件夹
//   - 通过回调函数实时报告进度和错误
//   - 使用内存映射和并发处理提高性能
//   - 对大文件采用抽样校验，对小文件进行完整校验
//   - 支持通过上下文取消同步操作
//
// 参数:
//   - ctx: 上下文，用于取消同步操作
//   - srcDir: 源目录路径
//   - dstDir: 目标目录路径
//   - overwrite: 是否覆盖目标目录中已存在的文件
//   - deleteExtra: 是否删除目标目录中源目录不存在的文件和文件夹
//   - progressCallback: 进度回调函数，可为nil
//   - errorCallback: 错误回调函数，可为nil
//   - deleteCallback: 删除回调函数，可为nil
//
// 返回:
//   - error: 如果同步过程中发生错误，返回相应的错误信息
func SyncDirectories(ctx context.Context, srcDir, dstDir string, overwrite bool, deleteExtra bool, progressCallback DirProgressCallback, errorCallback ErrorCallback, deleteCallback DeleteCallback) error {
	// 用于记录总文件数量、总大小和已复制的文件数量
	var totalFiles, totalSize int64
	var copiedFiles atomic.Int64 // 使用原子操作替代互斥锁
	var copiedBytes atomic.Int64 // 添加已复制字节数的原子计数器

	// 添加速度计算所需的变量
	startTime := time.Now()
	var lastUpdateTime atomic.Value
	lastUpdateTime.Store(startTime)
	var lastCopiedFiles atomic.Int64
	var lastCopiedBytes atomic.Int64

	// 检查上下文是否已取消
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
		// 继续执行
	}

	// 先遍历源目录，统计总文件数量和总大小
	err := filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
		// 检查上下文是否已取消
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
			// 继续执行
		}

		if err != nil {
			return err
		}
		if !info.IsDir() {
			totalFiles++
			totalSize += info.Size()
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("遍历源目录失败: %v", err)
	}

	// 创建并发控制通道，使用系统CPU核心数来确定并发数
	concurrency := runtime.NumCPU()
	if concurrency < 2 {
		concurrency = 2
	} else if concurrency > 8 {
		concurrency = 8 // 设置上限，避免过多并发
	}
	semaphore := make(chan struct{}, concurrency)
	defer close(semaphore)

	// 创建互斥锁保护共享变量
	var mu sync.Mutex
	// 用于跟踪错误
	var syncErrors []error
	var errorMu sync.Mutex

	// 如果启用了删除功能，创建一个映射来跟踪源目录中的所有文件和目录
	srcItems := make(map[string]bool)
	if deleteExtra {
		err := filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
			// 检查上下文是否已取消
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
				// 继续执行
			}

			if err != nil {
				return err
			}
			relPath, err := filepath.Rel(srcDir, srcPath)
			if err != nil {
				return err
			}
			// 使用 / 作为路径分隔符，以便在不同操作系统上保持一致
			relPath = filepath.ToSlash(relPath)
			srcItems[relPath] = true
			return nil
		})
		if err != nil {
			return fmt.Errorf("遍历源目录失败: %v", err)
		}
	}

	// 遍历源目录，进行文件复制
	err = filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
		// 检查上下文是否已取消
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
			// 继续执行
		}

		if err != nil {
			return err
		}

		// 如果是目录，确保目标目录存在
		if info.IsDir() {
			relPath, err := filepath.Rel(srcDir, srcPath)
			if err != nil {
				return err
			}
			dstPath := filepath.Join(dstDir, relPath)
			if err := os.MkdirAll(dstPath, info.Mode()); err != nil {
				return fmt.Errorf("无法创建目标目录 %s: %v", dstPath, err)
			}
			return nil
		}

		// 如果是文件，进行复制
		relPath, err := filepath.Rel(srcDir, srcPath)
		if err != nil {
			return err
		}
		dstPath := filepath.Join(dstDir, relPath)

		// 控制并发数
		select {
		case <-ctx.Done():
			return ctx.Err() // 如果上下文已取消，立即返回
		case semaphore <- struct{}{}:
			// 继续执行
		}

		go func() {
			defer func() {
				<-semaphore
				copiedFiles.Add(1) // 使用原子操作增加计数
			}()

			// 创建一个文件进度回调函数来更新当前文件的进度
			fileProgressCallback := func(fileProgress FileProgress) {
				if progressCallback != nil {
					// 创建一个副本，避免传递指针导致的并发问题
					progressCopy := fileProgress

					// 更新已复制的总字节数（只计算增量）
					if progressCopy.CopiedSize > 0 {
						// 如果是第一次调用或文件已完成，更新总字节数
						if progressCopy.ProgressPercent >= 100 {
							copiedBytes.Add(progressCopy.TotalSize)
						}
					}

					mu.Lock()
					// 计算目录复制速度
					now := time.Now()
					lastUpdate := lastUpdateTime.Load().(time.Time)
					elapsed := now.Sub(lastUpdate)

					var dirSpeedBytesPerSec int64
					var dirSpeedFilesPerSec float64

					if elapsed >= time.Second {
						// 计算每秒字节数和每秒文件数
						currentCopiedFiles := copiedFiles.Load()
						currentCopiedBytes := copiedBytes.Load()

						filesDelta := currentCopiedFiles - lastCopiedFiles.Load()
						bytesDelta := currentCopiedBytes - lastCopiedBytes.Load()

						dirSpeedBytesPerSec = int64(float64(bytesDelta) / elapsed.Seconds())
						dirSpeedFilesPerSec = float64(filesDelta) / elapsed.Seconds()

						// 更新上次更新时间和计数
						lastUpdateTime.Store(now)
						lastCopiedFiles.Store(currentCopiedFiles)
						lastCopiedBytes.Store(currentCopiedBytes)
					} else {
						// 使用总体平均速度
						totalElapsed := now.Sub(startTime).Seconds()
						if totalElapsed > 0 {
							dirSpeedBytesPerSec = int64(float64(copiedBytes.Load()) / totalElapsed)
							dirSpeedFilesPerSec = float64(copiedFiles.Load()) / totalElapsed
						}
					}

					currentProgress := float64(copiedFiles.Load()) / float64(totalFiles) * 100
					dirProgress := DirProgress{
						CurrentFile:      relPath,
						FileProgress:     &progressCopy, // 传递副本的指针
						CopiedFileCount:  copiedFiles.Load(),
						TotalFileCount:   totalFiles,
						ProgressPercent:  currentProgress,
						SpeedBytesPerSec: dirSpeedBytesPerSec,
						SpeedFilesPerSec: dirSpeedFilesPerSec,
					}
					progressCallback(dirProgress)
					mu.Unlock()
				}
			}

			if err := copyFileWithProgressCallback(ctx, srcPath, dstPath, overwrite, fileProgressCallback); err != nil {
				// 使用错误回调而不是直接打印
				if errorCallback != nil {
					errorCallback(srcPath, dstPath, err)
				}

				// 记录错误，但不终止整个同步过程
				errorMu.Lock()
				syncErrors = append(syncErrors, fmt.Errorf("复制文件 %s 到 %s 失败: %v", srcPath, dstPath, err))
				errorMu.Unlock()

				// 不再调用 cancel()，允许其他文件继续复制
			}
		}()

		return nil
	})

	if err != nil {
		return fmt.Errorf("遍历源目录失败: %v", err)
	}

	// 等待所有协程完成
	for i := 0; i < cap(semaphore); i++ {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case semaphore <- struct{}{}:
			// 继续执行
		}
	}

	// 如果上下文已取消，提前返回
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
		// 继续执行
	}

	// 如果启用了删除功能，删除目标目录中源目录不存在的文件和文件夹
	if deleteExtra {
		// 收集要删除的文件和目录
		var filesToDelete []string
		var dirsToDelete []string

		err := filepath.Walk(dstDir, func(dstPath string, info os.FileInfo, err error) error {
			// 检查上下文是否已取消
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
				// 继续执行
			}

			if err != nil {
				return nil // 忽略错误，继续遍历
			}

			// 获取相对路径
			relPath, err := filepath.Rel(dstDir, dstPath)
			if err != nil {
				return nil
			}

			// 跳过根目录
			if relPath == "." {
				return nil
			}

			// 使用 / 作为路径分隔符，以便在不同操作系统上保持一致
			relPath = filepath.ToSlash(relPath)

			// 如果在源目录中不存在，则标记为删除
			if !srcItems[relPath] {
				if info.IsDir() {
					dirsToDelete = append(dirsToDelete, dstPath)
				} else {
					filesToDelete = append(filesToDelete, dstPath)
				}
			}

			return nil
		})

		if err != nil {
			return fmt.Errorf("遍历目标目录失败: %v", err)
		}

		// 先删除文件
		for _, file := range filesToDelete {
			// 检查上下文是否已取消
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
				// 继续执行
			}

			if err := os.Remove(file); err != nil {
				if errorCallback != nil {
					errorCallback("", file, fmt.Errorf("删除文件失败: %v", err))
				}
				errorMu.Lock()
				syncErrors = append(syncErrors, fmt.Errorf("删除文件 %s 失败: %v", file, err))
				errorMu.Unlock()
			} else if deleteCallback != nil {
				deleteCallback(file)
			}
		}

		// 按照目录深度排序，确保先删除子目录
		sort.Slice(dirsToDelete, func(i, j int) bool {
			return len(strings.Split(dirsToDelete[i], string(os.PathSeparator))) > len(strings.Split(dirsToDelete[j], string(os.PathSeparator)))
		})

		// 删除目录
		for _, dir := range dirsToDelete {
			// 检查上下文是否已取消
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
				// 继续执行
			}

			if err := os.RemoveAll(dir); err != nil { // 使用 RemoveAll 替代 Remove
				if errorCallback != nil {
					errorCallback("", dir, fmt.Errorf("删除目录失败: %v", err))
				}
				errorMu.Lock()
				syncErrors = append(syncErrors, fmt.Errorf("删除目录 %s 失败: %v", dir, err))
				errorMu.Unlock()
			} else if deleteCallback != nil {
				deleteCallback(dir)
			}
		}
	}

	// 如果有错误，返回第一个错误
	if len(syncErrors) > 0 {
		return fmt.Errorf("同步过程中发生 %d 个错误，第一个错误: %v", len(syncErrors), syncErrors[0])
	}

	return nil
}

// MirrorSyncDirectories 使用镜像方式同步源目录到目标目录
//
// 功能:
//   - 将源目录中的所有文件和文件夹复制到目标目录
//   - 覆盖目标目录中已存在的文件
//   - 删除目标目录中存在但源目录不存在的文件和文件夹
//   - 通过回调函数实时报告进度和错误
//   - 使用内存映射和并发处理提高性能
//   - 对大文件采用抽样校验，对小文件进行完整校验
//   - 支持通过上下文取消同步操作
//
// 参数:
//   - ctx: 上下文，用于取消同步操作
//   - srcDir: 源目录路径
//   - dstDir: 目标目录路径
//   - progressCallback: 进度回调函数，可为nil
//   - errorCallback: 错误回调函数，可为nil
//   - deleteCallback: 删除回调函数，可为nil
//
// 返回:
//   - error: 如果同步过程中发生错误，返回相应的错误信息
func MirrorSyncDirectories(ctx context.Context, srcDir, dstDir string, progressCallback DirProgressCallback, errorCallback ErrorCallback, deleteCallback DeleteCallback) error {
	return SyncDirectories(ctx, srcDir, dstDir, true, true, progressCallback, errorCallback, deleteCallback)
}

// SyncDirectoriesSimple 同步源目录到目标目录（简化版，不支持取消）
//
// 参数:
//   - srcDir: 源目录路径
//   - dstDir: 目标目录路径
//   - overwrite: 是否覆盖目标目录中已存在的文件
//   - deleteExtra: 是否删除目标目录中源目录不存在的文件和文件夹
//   - progressCallback: 进度回调函数，可为nil
//   - errorCallback: 错误回调函数，可为nil
//   - deleteCallback: 删除回调函数，可为nil
//
// 返回:
//   - error: 如果同步过程中发生错误，返回相应的错误信息
func SyncDirectoriesSimple(srcDir, dstDir string, overwrite bool, deleteExtra bool, progressCallback DirProgressCallback, errorCallback ErrorCallback, deleteCallback DeleteCallback) error {
	return SyncDirectories(context.Background(), srcDir, dstDir, overwrite, deleteExtra, progressCallback, errorCallback, deleteCallback)
}

// MirrorSyncDirectoriesSimple 使用镜像方式同步源目录到目标目录（简化版，不支持取消）
//
// 参数:
//   - srcDir: 源目录路径
//   - dstDir: 目标目录路径
//   - progressCallback: 进度回调函数，可为nil
//   - errorCallback: 错误回调函数，可为nil
//   - deleteCallback: 删除回调函数，可为nil
//
// 返回:
//   - error: 如果同步过程中发生错误，返回相应的错误信息
func MirrorSyncDirectoriesSimple(srcDir, dstDir string, progressCallback DirProgressCallback, errorCallback ErrorCallback, deleteCallback DeleteCallback) error {
	return MirrorSyncDirectories(context.Background(), srcDir, dstDir, progressCallback, errorCallback, deleteCallback)
}
